/****************************************************************************
 *
 *  3DSHIER.C
 *
 *  (C) Copyright 1997 by Autodesk, Inc.
 *
 *  This program is copyrighted by Autodesk, Inc. and is licensed to you under
 *  the following conditions.  You may not distribute or publish the source
 *  code of this program in any form.  You may incorporate this code in object
 *  form in derivative works provided such derivative works are (i.) are de-
 *  signed and intended to work solely with Autodesk, Inc. products, and (ii.)
 *  contain Autodesk's copyright notice "(C) Copyright 1995 by Autodesk, Inc."
 *
 *  AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.  AUTODESK SPE-
 *  CIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
 *  A PARTICULAR USE.  AUTODESK, INC.  DOES NOT WARRANT THAT THE OPERATION OF
 *  THE PROGRAM WILL BE UNINTERRUPTED OR ERROR FREE.
 *
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include "3DSTYPE.H"
#include "3DSPRIM.H"
#include "3DSERR.H"
#include "CHUNKINF.H"
#include "CHUNK3DS.H"
#include "3DSFTKST.H"
#include "3DSHIER.H"
#include "KFUTILS.H"

static void s_assignParentNames(database3ds *db); 
static void s_computeNodeIds(database3ds *db);

/*------------------------------------------------
 | 
 |  nodeTags: Those tags that are in hierarchy
 | 
 +-----------------------------------------------*/
static chunktag3ds nodeTags[] = {
  TARGET_NODE_TAG,
  OBJECT_NODE_TAG,
  CAMERA_NODE_TAG,
  LIGHT_NODE_TAG,
  L_TARGET_NODE_TAG,
  SPOTLIGHT_NODE_TAG,
  (chunktag3ds)NULL,
};

static IdNodePtr nodeList = NULL;

/*----------------------------------------------------------
 | AssignParentNames
 |  convert parent ID's to parent names.
 |
 +----------------------------------------------------------*/
void AssignParentNames(database3ds *db)
{
  if (db == NULL)
    SET_ERROR_RETURN(ERR_INVALID_ARG); 

  InitNodeList();
  ParseDb(db);
  s_assignParentNames(db);
  ReleaseNodeList();
}

/*----------------------------------------------------------
 | ComputeNodeIds
 |  Assign NODE_ID's and parentId's
 |
 +----------------------------------------------------------*/
void ComputeNodeIds(database3ds *db)
{
  if (db == NULL)
    SET_ERROR_RETURN(ERR_INVALID_ARG); 
 
  InitNodeList();
  s_computeNodeIds(db);
  ReleaseNodeList();
 
}

/*---------------------------------------------------
 |
 | IsNode3ds: return True3ds if id is node id
 | 
 +---------------------------------------------------*/
short3ds IsNode3ds(chunktag3ds tagID)
{
  long3ds i = 0;

  while(nodeTags[i] != (chunktag3ds)NULL){
    if (tagID == nodeTags[i])
      return True3ds;
    i++;
  }
  return False3ds;
}

/*---------------------------------------------------
 |
 | InitNodeList
 | 
 +---------------------------------------------------*/
void InitNodeList()
{
  if (nodeList != NULL)
    ReleaseNodeList();
}

/*-------------------------------------------------------
 |
 | makeNode
 |  add node to linked list (uninitialized)
 |
 +------------------------------------------------------*/
static void makeNode(IdNodePtr *pPNode)
{
  IdNodePtr pNode, pId = nodeList;
  
  pNode = malloc(sizeof(IdNode));
  if (pNode != NULL){
    if (!pId){ /*--- First Node */
      nodeList = pNode;
    }
    else{  /*--- Add to list */
      while(pId->next)
    pId = pId->next;
      pId->next = pNode;
    }
  }
  /* else push error here */
    
  *pPNode = pNode;
}

static short3ds getChunkNodeId(chunk3ds *pChunk)
{
  chunk3ds *pIdChunk;

  short3ds retVal = kNoId;

  FindChunk3ds(pChunk, NODE_ID, &pIdChunk);
  if (pIdChunk != NULL){
    KFId *pKfId = ReadChunkData3ds(pIdChunk);
    if (pKfId != NULL) retVal = pKfId->id;
    FreeFileChunkData3ds(pIdChunk);    
  }

  return retVal;
}


/*-------------------------------------------------
 |
 | addToNodeList
 |   create a node, put node in list and fill-in
 |   structure.
 |
 +------------------------------------------------*/
static void addToNodeList(chunk3ds *pChunk)
{
  IdNodePtr pNewNode;
  chunk3ds *pHdrChunk, *pInstChunk;
  NodeHdr *pNodeHdr;

  makeNode(&pNewNode);
  if (pNewNode == NULL)
    return;

  FindChunk3ds(pChunk, NODE_HDR, &pHdrChunk);
  if (pHdrChunk == NULL)
    return;

  pNodeHdr = ReadChunkData3ds(pHdrChunk);
  if (pNodeHdr == NULL)
    return;
  
  /*--- FILL IN NODE DATA */
  pNewNode->name = strdup(pNodeHdr->objname);
  pNewNode->Id = getChunkNodeId(pChunk);  
  pNewNode->tag = pChunk->tag;
  pNewNode->parentId = pNodeHdr->parentindex;
  pNewNode->next = NULL;
  pNewNode->inst = NULL;
  
  /*--- Check for INSTANCE */
  if (pChunk->tag == OBJECT_NODE_TAG){
    FindChunk3ds(pChunk, INSTANCE_NAME, &pInstChunk);
    if (pInstChunk != NULL){
      InstanceName *data = ReadChunkData3ds(pInstChunk);
      if (data != NULL && data->name != NULL){
	pNewNode->inst = strdup(data->name);
	FreeFileChunkData3ds(pInstChunk);
      }
    }
  }
  FreeFileChunkData3ds(pHdrChunk); 
}

/*------------------------------------------------------------------
 |
 | PrintNodeList
 |   Simple array style print-out of what is in current nodeList.
 |
 +------------------------------------------------------------------*/
void PrintNodeList(FILE *outfile)
{
  IdNodePtr pId = nodeList;

  while(pId){
    if (pId->inst == NULL)
      fprintf(outfile, "ID:%d\tName:%s\tParent:%d\n",
          (int)pId->Id,
          pId->name,
          (int)pId->parentId);
    else
      fprintf(outfile, "ID:%d\tName:%s.%s\tParent:%d\n",
          (int)pId->Id,
          pId->name,
          pId->inst,
          (int)pId->parentId);
    pId = pId->next;
  }
}


static void printNode(FILE *outfile, IdNodePtr pId, int spaces)
{
  int i = 0;
  IdNodePtr nList = nodeList;

  while(i<spaces){
    fprintf(outfile, "-");
    i++;
  }
  fprintf(outfile, "%s\n", pId->name);
  
  while(nList){
    if (nList->parentId == pId->Id)
      printNode(outfile, nList, spaces+1);
    nList = nList->next;
  }
}

/*------------------------------------------------------------------
 |
 | PrintHierarchy
 |   Hierarchy print-out of parents and thier children
 |
 +------------------------------------------------------------------*/
void PrintHierarchy(FILE *outfile)
{
  IdNodePtr pId = nodeList;

  while(pId){
    /*--- Only do those w/o parents, rest do themselves */
    if (pId->parentId == -1)
      printNode(outfile, pId, 0);
    pId = pId->next;
  }
}



/*-------------------------------------------------------------------
 |
 |  ReleaseNodeList
 |
 +------------------------------------------------------------------*/
void ReleaseNodeList()
{
  IdNodePtr pId = nodeList, pNext;

  while (pId != NULL)
    {
      pNext = pId;
      if (pId->name)
    free(pId->name);
      if (pId->inst)
    free(pId->inst);
      pNext = pId->next;
      free(pId);
      pId = pNext;
    }
  nodeList = NULL;
}

/*----------------------------------------------------------------
 |
 | checkListNodeIds
 |   earlier versions (pre 3) of 3dstudio had no node ids, it
 |   simple used the order in which they came along, if so
 |   put in NODE IDs. Assuming that if one node has no ID the
 |   whole list get renumbered.
 |
 +---------------------------------------------------------------*/
static void checkListNodeIds()
{
  IdNodePtr pId = nodeList;

  while(pId){
    if (pId->Id == kNoId){ /*--- If somebody has no ID renumber list */
      short3ds index = 0;

      pId = nodeList;
      while(pId){
    pId->Id = index++;
    pId = pId->next;
      }
      break;
    }
    pId = pId->next;
  }
}


/*-------------------------------------------------------------------
 |
 | ParseDb
 |   Traverse the database and fill in the nodeList.
 |   InitNodeList first.
 |
 +-------------------------------------------------------------------*/
void ParseDb(database3ds *db)
{
  chunk3ds *pChunk, *pKfData;

  FindChunk3ds(db->topchunk, KFDATA, &pKfData);
  if (pKfData == NULL) 
    return;

  for (pChunk=pKfData->children; pChunk!=NULL; pChunk=pChunk->sibling){
    if (IsNode3ds(pChunk->tag) == True3ds)
      addToNodeList(pChunk);
  }
  
  checkListNodeIds();
}

static IdNodePtr findNodeById(short3ds id)
{
  IdNodePtr pId = nodeList;

  while (pId != NULL){
    if (pId->Id == id)
      break;
    pId = pId->next;
  }

  return pId;
}

#ifdef NOT_USED_IN_THIS_MODULE
static IdNodePtr findNodeByNameAndId(char3ds *name, chunktag3ds id)
{
  IdNodePtr pId = nodeList;

  while (pId != NULL){
    if (strcmp(pId->name, name) == 0 &&
    pId->tag == id)
      break;
    pId = pId->next;
  }

  return pId;
}
#endif

static IdNodePtr findObjNodeByName(char3ds *name)
{
  IdNodePtr pId = nodeList;
  
  /*--- extract names */
  if (strchr(name, (int)'.') != NULL){
    char3ds *pC, *pName, *pInst;
    
    /*--- Copy name and cutoff instance name */
    pName = strdup(name);
    pC = strchr(pName, (int)'.');
    *pC = (char3ds)0;
    
    /*--- point to instance part of orignal name string */
    pInst = strchr(name, (int)'.') + 1;
    
    while (pId != NULL){
      if (strcmp(pId->name, pName) == 0 && 
      strcmp(pId->inst, pInst) == 0){
    break;
      }
      pId = pId->next;
    }
    free(pName);
  }
  else {  /*--- No Instance name */
     while (pId != NULL){
	if (strcmp(pId->name, name) == 0)
	{
	   if (pId->inst == NULL) break;
	   if (pId->inst[0] == 0) break;
	}
	pId = pId->next;
     }
  }    
  return pId;
}


/*-----------------------------------------------------
 |
 | s_AssignParentNames
 |   Traverse keyframe data and assign parent
 |   names to its own chunk PARENT_NAME which is
 |   is a child of NODE_HDR.
 | 
 |  NODE_HDR
 |    PARENT_NAME
 +----------------------------------------------------*/
static void s_assignParentNames(database3ds *db)
{
  chunk3ds *pChunk, *pKfData, *pHdrChunk, *pNameChunk, *pIdChunk;
  int i = 0;
  KFId *pKfId;
  IdNodePtr pIdNode, pIdParentNode;
  char3ds *pName = NULL, *pInst = NULL;
  
  FindChunk3ds(db->topchunk, KFDATA, &pKfData);
  if (pKfData == NULL) 
    return;
  
  /*--- Find chunks in KFRAMER */
  while(nodeTags[i] != (chunktag3ds)NULL){
    FindChunk3ds(pKfData, nodeTags[i], &pChunk);
    if (pChunk){
      
      while(pChunk){
    FindChunk3ds(pChunk, NODE_HDR, &pHdrChunk);
    if (pHdrChunk == NULL)
      goto NEXT;
    
    FindChunk3ds(pChunk, NODE_ID, &pIdChunk);
    if (pIdChunk == NULL)
      goto NEXT;

    pKfId = ReadChunkData3ds(pIdChunk);
    if (pKfId == NULL)
      goto NEXT;
    
    /*--- Find table entry for node of interest */
    pIdNode = findNodeById(pKfId->id);
    /*--- No ID (bad) or No Parent (ok) */
    if (pIdNode == NULL || pIdNode->parentId == -1)
      goto NEXT;
    
    /*--- Find table entry for PARENT */
    pIdParentNode = findNodeById(pIdNode->parentId);
    if (pIdParentNode != NULL){
      pName = pIdParentNode->name;
      pInst = pIdParentNode->inst;
    }
    
    if (pName == NULL)
      goto NEXT;
    
    /*--- Concatenate names if there is an inst name  */
    if (pInst != NULL)
      {
        char3ds *pC;
        
        pC = malloc(strlen(pName) + strlen(pInst) + 2);
        sprintf(pC, "%s.%s", pName, pInst);
        pName = pC;
      }
    
    /*--- If PARENT chunk exists, copy into it */
    FindChunk3ds(pHdrChunk, PARENT_NAME, &pNameChunk);
    if (pNameChunk != NULL){
      InstanceName *pInstName;
      
      pInstName = ReadChunkData3ds(pNameChunk);
      if (pInstName == NULL){
        if (pInst) free(pName);
        goto NEXT;
      }
      strcpy(pInstName->name, pName);
    }
    else /*--- Create PARENT_NAME chunk */
      kfAddParentName(pHdrChunk, pName);
    
    if (pInst) free(pName);    
    
      NEXT:FindNextChunk3ds(pChunk->sibling, nodeTags[i], &pChunk);
      }
    }
    i++;
  }
}

static void setNodeId(chunk3ds *pChunk, short3ds id)
{
  chunk3ds *pIdChunk;
  KFId *pKfId;

  FindChunk3ds(pChunk, NODE_ID, &pIdChunk);
  if (pIdChunk == NULL)
    return;
  pKfId = ReadChunkData3ds(pIdChunk);
  if (pKfId == NULL)
    return;

  pKfId->id = id;
}
/*-----------------------------------------------------
 |
 | s_computeNodeIds
 |   Traverse keyframe data and assign NODE_ID's
 |   and fill in parentId's of named parents.
 |
 | Note: naming scheme
 |
 |  NODE                         PARENT
 |  parentname:foo.bar  ---->    name:foo
 |                               instance:bar
 |
 |  parentname:foo      ---->    name:foo
 |                               instance:(NULL)
 |
 +----------------------------------------------------*/
static void s_computeNodeIds(database3ds *db)
{
  chunk3ds *pChunk, *pKfData;
  short3ds i = 0;
  IdNodePtr pId;

  InitNodeList();

  FindChunk3ds(db->topchunk, KFDATA, &pKfData);
  if (pKfData == NULL) 
    return;

  /*--- Set Node ID's and build list */
  for (pChunk=pKfData->children; pChunk!=NULL; pChunk=pChunk->sibling){
    if (pChunk->tag == AMBIENT_NODE_TAG){
      setNodeId(pChunk, -1);
      continue;
    }

    if (IsNode3ds(pChunk->tag) == True3ds){
      setNodeId(pChunk, i);
      addToNodeList(pChunk);
      i++;
    }    
  }

  /*--- Assign parent IDs base on parent names */
  for (pChunk=pKfData->children; pChunk!=NULL; pChunk=pChunk->sibling){
    chunk3ds *pHdrChunk, *pNameChunk;
    NodeHdr *pNodeHdr;
    InstanceName *pInstName;

    if (IsNode3ds(pChunk->tag) == False3ds)
      continue;

    /*--- Get Header and Data */
    FindChunk3ds(pChunk, NODE_HDR, &pHdrChunk);
    if (pHdrChunk == NULL)
      continue;
    pNodeHdr = ReadChunkData3ds(pHdrChunk);
    if (pNodeHdr == NULL)
      continue;

    /*--- Get PARENT_NAME and Data */
    FindChunk3ds(pHdrChunk, PARENT_NAME, &pNameChunk);
    /*--- No parent chunk ---> no parent */
    if (pNameChunk == NULL){
      pNodeHdr->parentindex = -1;
      continue;
    }
    pInstName = ReadChunkData3ds(pNameChunk);
    if (pInstName == NULL)
      continue;

    /*--- Find the table entry of the parent */
    pId = findObjNodeByName(pInstName->name);

    /*--- If there is an Id use it, else no parent */
    pNodeHdr->parentindex = (short3ds)((pId)? pId->Id : -1);
    FreeFileChunkData3ds(pNameChunk);
  }
}

